from base import *
from sqlalchemy.orm import relationship, backref
from sqlalchemy import ForeignKey, Sequence
from relacion import *
from sqlalchemy import func
from itematributovalor import *
from lineabase import *

class Item(Base):
    """
    Clase que crea la tabla Item 
    """
    __tablename__           = 'item'
    id                      = Column(Integer, Sequence('item_id_seq'), primary_key=True)
    numfase                 = Column(Text)
    nombre                  = Column(Text, nullable=False)
    descripcion             = Column(Text)
    version                 = Column(Integer, nullable=False)
    prioridad               = Column(Integer, nullable=False)
    complejidad             = Column(Integer, nullable=False)
    estado                  = Column(Text, nullable=False)
    idlineabase             = Column(Integer, ForeignKey('lineabasea.id'))
    idtipoitem              = Column(Integer, ForeignKey('tipoitem.id'))
    idfase                  = Column(Integer, ForeignKey('fase.id'))
    tipoitem                = relationship("Tipoitem", backref="items")
    valoratributo_x_version = relationship("ItemAtributoValor", backref="item")
    fase                    = relationship("Fase")
    
    def __init__(self, numfase, nombre, descripcion, version, prioridad, complejidad, estado, idlineabase, idtipoitem, idfase):
        """
        Metodo de instancia constructor que inicializa los parametros del objeto item
        @type numfase: Text
        @param numfase: campo que indica la numeracion del item dentro de una fase
        @type nombre: Text
        @param nombre: campo que almacenara el nombre del item
        @type descripcion: Text
        @param nombre: campo que almacenara la descripcion del item
        @type version: Integer
        @param version: campo que almacenara la version del item
        @type prioridad: Integer
        @param prioridad: campo que almacenara la prioridad del item
        @type complejidad: Integer
        @param complejidad: campo que almacenara la complejidad del item
        @type estado: Text
        @param estado: campo que almacenara el estado del item
        @type idlineabase: Integer
        @param idlineabase: campo que indica a que linea base pertenece el item
        @type idtipoitem: Integer
        @param idtipoitem: campo que indica el tipo del item
        @type idfase: Integer
        @param idfase: campo que indicia la fase al que pertenece el item
        """
        self.numfase = numfase
        self.nombre = nombre
        self.descripcion = descripcion
        self.version = version
        self.prioridad = prioridad
        self.complejidad = complejidad
        self.estado = estado
        self.idlineabase = idlineabase
        self.idtipoitem = idtipoitem
        self.idfase = idfase

    def eliminar_dependencias(self):
        """
        Metodo de instancia que elimina todas los relaciones de un registro de la tabla Items
        para que pueda ser eliminado.
        @type self: item
        @param self: referencia al objeto que llama el metodo en este caso item.
        """
        # TODO : implementar
        pass

    def get_antecesores(self,tipo='ALL'):
        """
        Metodo que retorna las relaciones existentes entre el item y sus antecesores.
        @type self: Item
        @param self: referencia al objeto que llama el metodo en este caso item.
        @type tipo: Integer
        @param tipo: especifica el tipo de relacion que se retorna
        """
        # Obtenemos los ids de los items de origen
        id_antecesores = None
        if tipo=='PH' or tipo=='AS':
            # solo los ids de los antecesores o padres
            id_antecesores = bdsession.query(Relacion.idorigen, func.max(Relacion.vorigen)).filter_by(iddestino=self.id,vdestino=self.version,tipo=tipo).group_by(Relacion.idorigen, Relacion.iddestino).all()
        else:
            # ids de los antecesores y padres
            id_antecesores = bdsession.query(Relacion.idorigen, func.max(Relacion.vorigen)).filter_by(iddestino=self.id,vdestino=self.version).group_by(Relacion.idorigen, Relacion.iddestino).all()
        
        relaciones_validas    = []
        relaciones_invalidas  = []
        relaciones_eliminadas = []
        for element in id_antecesores:
            id_item = element[0]
            version = element[1]
            # Extraemos la relacion de la BD
            relacion = bdsession.query(Relacion).filter_by(iddestino=self.id,vdestino=self.version,idorigen=id_item,vorigen=version).first()
            # item origen
            origen = bdsession.query(Item).filter_by(id=relacion.idorigen).first()
            # item destino
            destino = bdsession.query(Item).filter_by(id=relacion.iddestino).first()
            # controlamos que las versiones sean consistentes
            if origen.version == int(relacion.vorigen):
                # Filtramos las relaciones de acuerdo al estado de la relacion
                if relacion.estado == 'VALIDO':
                    relaciones_validas.append(relacion)
                elif relacion.estado == 'INVALIDO':
                    relaciones_invalidas.append(relacion)
                else:
                    relaciones_eliminadas.append(relacion)
        
        return { 'validas':relaciones_validas,'invalidas':relaciones_invalidas,'eliminadas':relaciones_eliminadas}
    
    def get_sucesores(self,tipo='ALL'):
        """
        Metodo que retorna las relaciones existentes entre el item y sus sucesores.
        @type self: Item
        @param self: referencia al objeto que llama el metodo en este caso item.
        @type tipo: Integer
        @param tipo: especifica el tipo de relacion que se retorna
        """
        # Obtenemos los ids de los items de destino
        id_sucesores = None
        if tipo=='PH' or tipo=='AS':
            # solo los ids de los sucesores o hijos
            id_sucesores = bdsession.query(Relacion.iddestino, func.max(Relacion.vdestino)).filter_by(idorigen=self.id,vorigen=self.version,tipo=tipo).group_by(Relacion.iddestino, Relacion.idorigen).all()
        else:
            # ids de los sucesores e hijos
            id_sucesores = bdsession.query(Relacion.iddestino, func.max(Relacion.vdestino)).filter_by(idorigen=self.id,vorigen=self.version).group_by(Relacion.iddestino, Relacion.idorigen).all()
        
        relaciones_validas    = []
        relaciones_invalidas  = []
        relaciones_eliminadas = []
        for element in id_sucesores:
            id_item = element[0]
            version = element[1]
            # Extraemos la relacion de la BD
            relacion = bdsession.query(Relacion).filter_by(idorigen=self.id,vorigen=self.version,iddestino=id_item,vdestino=version).first()
            # item origen
            origen = bdsession.query(Item).filter_by(id=relacion.idorigen).first()
            # item destino
            destino = bdsession.query(Item).filter_by(id=relacion.iddestino).first()
            # controlamos que las versiones sean consistentes
            if destino.version == int(relacion.vdestino):
                # Filtramos las relaciones de acuerdo al estado de la relacion
                if relacion.estado == 'VALIDO':
                    relaciones_validas.append(relacion)
                elif relacion.estado == 'INVALIDO':
                    relaciones_invalidas.append(relacion)
                else:
                    relaciones_eliminadas.append(relacion)
        
        return { 'validas':relaciones_validas,'invalidas':relaciones_invalidas,'eliminadas':relaciones_eliminadas}
    
    def get_relaciones(self,version=-1):
        """
        Metodo que retorna todas las relaciones del item en una version dada.
        @type self: Item
        @param self: referencia al objeto que llama el metodo en este caso item.
        @type version: Integer
        @param version: version en la cual queremos obtener las relaciones, por defecto es la version actual.
        """
        # Por defecto, la version es la version actual
        if version == -1:
            version = self.version
        tmp = self.version
        self.version = version
        # Obtenemos las relaciones
        ante = self.get_antecesores('ALL')
        suce = self.get_sucesores('ALL')
        # Ponemos las relaciones todas juntas
        rels = ante['validas'] + suce['validas']
        # Restauramos la version del item
        self.version = tmp
        # Retornamos la lista de relaciones
        return rels
    
    def hijos_aprobados(self):
        """
        Metodo que indica si todos los hijos de un item se encuentran aprobados
        @type self: Item
        @param self: referencia al objeto que llama el metodo en este caso item.
        """
        # Obtenemos las relaciones con los hijos
        sucesores = self.get_sucesores('PH')['validas']
        for r in sucesores:
            # Obtenemos el hijo desde la BD
            hijo = bdsession.query(Item).filter_by(id=r.iddestino).first()
            if hijo.estado != u'APROBADO':
                return False
        return True
    def padres_aprobados(self):
        """
        Metodo que indica si todos los padres de un item se encuentran aprobados
        @type self: Item
        @param self: referencia al objeto que llama el metodo en este caso item.
        """
        # Obtenemos las relaciones con los padres
        antecesores = self.get_antecesores('PH')['validas']
        for r in antecesores:
            # Obtenemos el hijo desde la BD
            padre = bdsession.query(Item).filter_by(id=r.idorigen).first()
            if padre.estado != u'APROBADO':
                return False
        return True
    
    def diff(self,version):
        """
        Metodo que indica las diferencias entre los atributos de la version actual
        del item con otra version anterior.
        @type self: Item
        @param self: referencia al objeto que llama al metodo.
        @type version: Integer
        @param version: version anterior del item a comparar
        """
        def is_in(L,e):
            """
            Metodo auxiliar que controla si un elemento de tipo ItemAtributoValor
            existe dentro de una lista de este tipo de elementos.
            @type L: List
            @param L: lista de elementos ItemAtributoValor.
            @type e: ItemAtributoValor
            @param e: elemento que se desea comprobar si existe dentro de la lista.
            """
            for x in L:
                if e.idatributo == x.idatributo and e.valor == x.valor:
                    return True
            return False
        
        # atributos actuales
        atributos_act = bdsession.query(ItemAtributoValor).filter_by(iditem=self.id,version=self.version).all()
        # atributos anteriores
        atributos_ant = bdsession.query(ItemAtributoValor).filter_by(iditem=self.id,version=version).all()
        # obtenemos que atributos anteriores difieren de los anteriores
        atributo_diff = []
        for iav in atributos_act:
            if not is_in(atributos_ant,iav):
                atributo_diff.append(iav)
        # retornamos los resultados
        return atributo_diff
